<template>
  <a-spin :spinning="loading" class="full-screen-spin">
    <div class="algo-test-edit">
      <div class="header">
        <div class="logo fc">
          <img src="@/assets/img/header-logo.svg" alt="" />
        </div>
        <div class="crumbs fc">
          <span class="cursor-pointer" @click="goAlgoTest">算法测试 /</span>
          <span class="ml-8 task-name">{{ pageForm.name }}</span>
        </div>
      </div>
      <div class="tools">
        <div class="task-status" :class="[statusMap.className]">
          <div class="icon-box fc">
            <i class="iconfont" :class="[statusMap.iconName]"></i>
          </div>
          <div class="text fc">{{ statusMap.name }}</div>
        </div>
        <div class="ml-20 task-info">
          <div>
            <span class="label">测试指标</span>
            <span class="ml-24 value">
              <span v-for="(norms, index) in pageForm.taskNormDetails" :key="index" class="value__item">{{ norms.name }}</span>
            </span>
          </div>
          <div class="mt-4">
            <span class="label">创建时间</span>
            <span class="ml-24 value">{{ pageForm.createTime }}</span>
          </div>
        </div>
        <div class="flex-1"></div>
        <div class="btn-list">
          <div>
            <a-button v-if="canEdit && checkPermissions(pageForm?.createType, pageForm?.readOnly)" type="primary" @click="runTask"
              >运行</a-button
            >
          </div>
          <div>
            <i
              v-if="canDownload"
              class="ml-20 cursor-pointer icon-download-2-line iconfont"
              @click="downloadTaskRecord(pageForm.taskRecordId!)"
            ></i>
          </div>
        </div>
      </div>
      <div ref="mainRef" class="main">
        <left-collapse
          ref="leftCollapseRef"
          class="left-collapse my-scrollbar"
          :code-editor-value="codeEditorValue"
          :can-edit="canEdit"
          :status="pageForm.status"
          @updateCode="updateCode"
        />
        <div ref="codeAndResRef" class="code-and-res">
          <code-editor
            v-show="!isWaitRun"
            ref="codeEditorRef"
            v-model:value="codeEditorValue"
            class="code-editor"
            :style="{ height: `calc(100% - ${showRes ? resBoxHeight : 0}px)` }"
          />
          <div v-show="isWaitRun" class="code-editor fc">
            <a-spin tip="资源准备中..." />
          </div>
          <transition>
            <div v-if="showRes" class="res-box" :style="{ height: resBoxHeight + 'px' }">
              <div ref="resizeBoxRef" class="resize-handle fc">
                <svg width="18" height="3" viewBox="0 0 18 3" fill="none" xmlns="http://www.w3.org/2000/svg">
                  <circle cx="2" cy="1.5" r="1.5" fill="white" />
                  <circle cx="9" cy="1.5" r="1.5" fill="white" />
                  <circle cx="16" cy="1.5" r="1.5" fill="white" />
                </svg>
              </div>
              <div class="res-title no-select">运行结果 <a-button class="ml-8" type="link" @click="refreshTaskResult">刷新</a-button></div>
              <div v-if="isShowLogLoading" class="res-body fc running">
                <a-spin tip="资源准备中..." />
              </div>
              <div v-else class="res-body">
                <pre>{{ taskResult }}</pre>
              </div>
            </div>
          </transition>
        </div>
      </div>
    </div>
  </a-spin>
</template>

<script lang="ts" setup>
  import { useDocumentVisibility, useDraggable, useEventListener } from '@vueuse/core'
  import { message, Modal } from 'ant-design-vue'
  import { computed, nextTick, ref } from 'vue'
  import { watch } from 'vue'
  import { onUnmounted } from 'vue'
  import { useRequest } from 'vue-request'
  import { useRoute, useRouter } from 'vue-router'

  import CodeEditor from '@/components/CodeEditor/index.vue'
  import { algoTestApi, k8sApi } from '@/services'
  import { getDistanceFromTop } from '@/utils'
  import { checkPermissions } from '@/utils/share'

  import { TaskDetail } from '../types'
  import { downloadTaskRecord } from '../utils'
  import LeftCollapse from './components/LeftCollapse.vue'
  import { TaskLogRes } from './types'

  const route = useRoute()
  const router = useRouter()
  const taskId = route.params.id as string
  const {
    data: taskDetailRes,
    loading: taskDetailLoading,
    runAsync: getTaskDetailRun
  } = useRequest<ResponseData<any>>(algoTestApi.detail(taskId))

  const leftCollapseRef = ref<InstanceType<typeof LeftCollapse>>()
  const codeEditorRef = ref<InstanceType<typeof CodeEditor>>()
  const codeEditorValue = ref('') //编辑器当前内容

  const updateCode = (str: string) => {
    codeEditorRef.value?.setValue(str)

    if (canEdit.value) {
      codeEditorRef.value?.setReadOnly(false)
    }
  }

  const pageForm = ref<TaskDetail>({
    taskId,
    createTime: '',
    taskNormDetails: [],
    status: 0,
    name: '',
    bucketList: [],
    modelList: [],
    dataIds: [],
    modelIds: [],
    codeTree: { childList: [] },
    mainPath: '',
    url: '',
    taskRecordId: '',
    runResult: '',
    buckets: [], //接口返回的数据集
    models: [] //接口返回的模型
  })

  // 是否显示运行结果
  const showRes = computed(() => [2, 3, 4, 5].includes(pageForm.value.status))
  // 是否可编辑
  const canEdit = computed(() => [0, 3, 4, 5].includes(pageForm.value.status))
  // 是否可下载
  const canDownload = computed(() => [3, 4, 5].includes(pageForm.value.status))
  // 是否需要同步任务状态
  const needSyncStatus = computed(() => [1, 2, 6].includes(pageForm.value.status))
  // 是否是待运行
  const isWaitRun = computed(() => [1, 6].includes(pageForm.value.status))

  const taskStatus = [
    {
      status: 0,
      name: '未运行',
      iconName: 'icon-play-fill',
      className: ''
    },
    {
      status: 1,
      name: '待运行',
      iconName: 'icon-play-fill',
      className: 'wait'
    },
    {
      status: 2,
      name: '运行中',
      iconName: 'icon-play-fill',
      className: 'running'
    },
    {
      status: 3,
      name: '成功',
      iconName: 'icon-check-double-line',
      className: 'success'
    },
    {
      status: 4,
      name: '失败',
      iconName: 'icon-close-circle-fill',
      className: 'error'
    },
    {
      status: 5,
      name: '超时',
      iconName: 'icon-time-fill',
      className: 'error'
    },
    {
      status: 6,
      name: '准备中',
      iconName: 'icon-play-fill',
      className: ''
    },
    {
      status: 7,
      name: '已停止',
      iconName: 'icon-play-fill',
      className: 'error'
    },
    {
      status: 8,
      name: '启动中',
      iconName: 'icon-play-fill',
      className: 'running'
    }
  ]

  const statusMap = computed(() => {
    const status = taskStatus.find(item => item.status === pageForm.value.status)
    return status || taskStatus[0]
  })

  const getTaskDetail = async () => {
    await getTaskDetailRun()
    setDetailInfo(taskDetailRes.value?.data)
  }

  /* 将详情数据写入页面 */
  const setDetailInfo = (data: TaskDetail) => {
    Object.assign(pageForm.value, data)

    // 对数据集进行处理
    pageForm.value.buckets!.forEach((it: any) => {
      pageForm.value.bucketList.push({
        key: it.dataId,
        title: it.fileName,
        isLeaf: it.fileType === 1,
        checked: true,
        deleted: it.deleted
      })
      pageForm.value.dataIds.push(it.dataId)
    })
    // 对模型进行处理
    pageForm.value.models!.forEach((it: any) => {
      pageForm.value.modelList.push({
        key: it.modelId,
        title: it.modelName,
        isLeaf: it.fileType === 1,
        checked: true,
        deleted: it.deleted
      })
      pageForm.value.modelIds.push(it.modelId)
    })

    // 如果运行过则获取运行结果
    if (showRes.value) {
      getLog()
    }

    nextTick(() => {
      const data = pageForm.value

      // 有主文件路径则说明已经运行过，需给左侧数据赋值
      if (data.mainPath) {
        leftCollapseRef.value?.updateCheckedDataBucketList(data.bucketList, data.dataIds)
        leftCollapseRef.value?.updateCheckedModelList(data.modelList, data.modelIds)

        if (data.status !== 1 && data.status !== 6 && data.codeTree) {
          leftCollapseRef.value?.setCodeTreeAndSource(data.url, data.codeTree.childList, [data.mainPath])
        }
      }

      codeEditorRef.value?.setReadOnly(true)
      getCodeAndResHeight()
    })
  }

  const loading = computed(() => {
    if (!leftCollapseRef.value) {
      return true
    }
    return leftCollapseRef.value.pageLoading || taskDetailLoading.value || runTaskLoading.value
  })

  const { loading: runTaskLoading, runAsync: runTaskRun } = useRequest(algoTestApi.run)

  const runTask = async () => {
    if (!leftCollapseRef.value) return
    if (leftCollapseRef.value.dataBucketIds.length === 0) {
      return message.error('请选择数据集')
    }
    if (leftCollapseRef.value.modelIds.length === 0) {
      return message.error('请选择模型')
    }
    if (leftCollapseRef.value.codeResource === '') {
      return message.error('请选择代码')
    }

    pageForm.value.dataIds = leftCollapseRef.value.dataBucketIds
    pageForm.value.modelIds = leftCollapseRef.value.modelIds
    pageForm.value.url = leftCollapseRef.value.codeResource

    // 保存代码
    await leftCollapseRef.value.saveCurrentCode()

    await runTaskRun(pageForm.value)

    Modal.confirm({
      title: '任务已运行',
      content: '任务正在执行中...',
      okText: '查看详情',
      cancelText: '返回任务列表',
      centered: true,
      onOk() {
        router.go(0)
      },
      onCancel() {
        goAlgoTest()
      }
    })
  }

  const goAlgoTest = () => {
    router.push('/algo-test/task-list')
  }
  /* 同步任务状态逻辑--------------------------------------------- */
  let syncStatusTimer: any = null

  const {
    data: taskDetailRes2,
    runAsync: getTaskDetailRun2 // 用于同步状态，不需要loading
  } = useRequest<ResponseData<any>>(algoTestApi.detail(taskId))

  const syncStatus = async () => {
    if (!needSyncStatus.value) {
      clearInterval(syncStatusTimer)
      return
    }
    await getTaskDetailRun2()

    const newStatus = taskDetailRes2.value?.data.status

    if (newStatus !== pageForm.value.status) {
      router.go(0)
    }
  }

  syncStatusTimer = setInterval(syncStatus, 3000)

  /* 获取任务输出日志逻辑 ------------------------------------------------ */
  const visibility = useDocumentVisibility()
  watch(visibility, val => {
    if (val === 'hidden') {
      resetLogInfo()
      return
    }
    if (showRes.value) {
      getLog()
    }
  })
  const taskResult = ref('') //任务显示结果
  const isShowLogLoading = ref(true) //是否显示日志加载中
  let controller: null | AbortController = null //实时日志的fetch取消令牌

  const showLogRefresher = ref(false) //是否显示刷新按钮
  const getLog = async () => {
    // 任务记录id
    const taskRecordId = pageForm.value.taskRecordId
    const { data } = (await k8sApi.log({ id: taskRecordId, follow: true })) as ResponseData<TaskLogRes>
    // 是否显示刷新按钮取决于是否已经执行完毕
    showLogRefresher.value = !data.selectApiFlag
    //判断是否已经执行完毕
    if (!data.selectApiFlag) {
      taskResult.value = data.log
      isShowLogLoading.value = false
      return
    }
    onlineLog(data.url, data.accessToken)
  }

  const refreshTaskResult = () => {
    resetLogInfo()
    getLog()
  }

  // 重置日志信息
  const resetLogInfo = () => {
    isShowLogLoading.value = true
    taskResult.value = ''
    controller?.abort()
    controller = null
  }

  // 长链接获取打印日志
  const onlineLog = async (url: string, token: string) => {
    // 如果已经有请求了，则不再发起请求
    if (controller) return

    url = '/k8s' + url

    // 设置请求头
    const headers = {
      authorization: token,
      'Content-Type': 'text/plain' // 根据实际情况设置其他请求头
    }

    // 初始化取消令牌
    controller = new AbortController()
    const signal = controller.signal

    const response = await fetch(url, { headers, signal })

    isShowLogLoading.value = false

    if (!response.body) {
      console.error('ReadableStream not supported')
      return
    }

    // 获取 ReadableStream 对象
    const reader = response.body.getReader()
    const decoder = new TextDecoder()

    // 递归处理每个数据块
    async function read() {
      const { done, value } = await reader.read()

      if (done) {
        return
      }
      const text = decoder.decode(value)
      taskResult.value += text
      nextTick(() => {
        document.querySelector('.res-body')?.scrollTo(0, 9999999)
      })

      // 递归调用，处理下一个数据块
      read()
    }

    // 开始读取数据流
    read()
  }
  /* 拖拽改变大小逻辑 ------------------------------------------------ */
  const codeAndResRef = ref<HTMLElement>()
  const mainRef = ref<HTMLElement>()
  const resizeBoxRef = ref<HTMLElement>()
  const resBoxHeight = ref(460)

  const resizeHandler = (mousePosition: any) => {
    // 编辑器高度 = 鼠标当前位置 - main.offsetTop
    let editorHeight = mousePosition.y - getDistanceFromTop(mainRef.value)
    editorHeight = editorHeight > 100 ? editorHeight : 100

    let newHeight = mainRef.value!.offsetHeight - editorHeight
    newHeight = newHeight > 100 ? newHeight : 100

    resBoxHeight.value = newHeight
  }

  useDraggable(resizeBoxRef, { onMove: resizeHandler })
  useEventListener(window, 'resize', () => {
    const windowWidth = window.innerWidth > 1400 ? window.innerWidth : 1400
    const width = windowWidth - 326 //326为左侧宽度
    codeEditorRef.value?.setEditorSize({ width })
  })

  // 在页面初始化时获取容器的高度并将高度的一半传递给resBoxHeight.value
  const getCodeAndResHeight = () => {
    console.log(codeAndResRef.value?.clientHeight)
    if (codeAndResRef.value?.clientHeight) {
      resBoxHeight.value = codeAndResRef.value?.clientHeight / 2
    }
  }

  /*  ------------------------------------------------------------------- */
  getTaskDetail()

  onUnmounted(() => {
    clearInterval(syncStatusTimer)
  })
</script>

<style lang="less" scoped>
  @import '@/styles/mixins.less';

  .algo-test-edit {
    display: flex;
    flex-direction: column;
    width: 100vw;
    min-width: @min-width;
    height: 100vh;
    min-height: @min-height;

    .header {
      display: flex;
      height: 44px;
      border-bottom: 1px solid @border2;

      .logo {
        width: 60px;
        height: 44px;
        border-right: 1px solid @border2;
      }

      .crumbs {
        padding-left: 10px;

        .task-name {
          color: @text01;
          .font16b();
        }
      }
    }

    .tools {
      display: flex;
      align-items: center;
      padding: 20px 24px;
      border-bottom: 1px solid @border2;

      .task-status {
        overflow: hidden;
        border-radius: 2px;

        .icon-box {
          width: 56px;
          height: 36px;
          background: rgba(25, 25, 48, 0.045);

          .iconfont {
            font-size: 24px;
            color: #505062;
          }
        }

        .text {
          font-family: 'Noto Sans SC';
          color: #fff;
          background-color: @text03;
          font-style: normal;
          font-weight: 400;
          line-height: 22px;
          .font12();
        }

        &.wait {
          .icon-box {
            background-color: rgba(70, 134, 237, 0.1);

            .iconfont {
              color: rgba(70, 134, 237, 1);
            }
          }

          .text {
            background-color: rgba(70, 134, 237, 1);
          }
        }

        &.running {
          .icon-box {
            background-color: rgba(13, 76, 202, 0.1);

            .iconfont {
              color: rgba(13, 76, 202, 1);
            }
          }

          .text {
            background-color: rgba(13, 76, 202, 1);
          }
        }

        &.success {
          .icon-box {
            background-color: rgba(92, 201, 105, 0.1);

            .iconfont {
              color: rgba(92, 201, 105, 1);
            }
          }

          .text {
            background-color: rgba(92, 201, 105, 1);
          }
        }

        &.error {
          .icon-box {
            background-color: rgba(214, 24, 96, 0.1);

            .iconfont {
              color: rgba(214, 24, 96, 1);
            }
          }

          .text {
            background-color: rgba(214, 24, 96, 1);
          }
        }
      }

      .task-info {
        .font14();

        .label {
          color: @text02;
        }

        .value {
          color: @text01;

          .value__item + .value__item {
            position: relative;
            margin-left: 16px;

            &::before {
              position: absolute;
              content: '';
              top: 50%;
              transform: translateY(-50%);
              left: -8px;
              width: 1px;
              height: 12px;
              background: @border2;
            }
          }
        }
      }

      .btn-list {
        display: flex;
        justify-content: space-between;
        align-items: center;
        width: 96px;
      }
    }

    .main {
      display: flex;
      overflow: hidden;
      height: 0;
      flex: 1;
      // flex-shrink: 0;

      .left-collapse {
        overflow: hidden;
        width: 326px;
        min-width: 326px;
        height: 100%;
      }

      .code-and-res {
        flex: 1;
        height: 100%;

        .code-editor {
          height: 100%;
        }

        .res-box {
          display: flex;
          width: 100%;
          background-color: @background2;
          flex-direction: column;

          .resize-handle {
            display: none;
            width: 100%;
            height: 7px;
            background: #ccc;
            cursor: ns-resize;
            transition: all 0.1s;

            &:active {
              background: @primary3;
            }
          }

          .res-title {
            display: flex;
            align-items: center;
            padding: 0 16px;
            margin-top: 18px;
            height: 38px;
            color: @text01;
            background-color: @background3;
            border-style: solid;
            border-width: 1px 0;
            border-color: #d9d9db;
          }

          .res-body {
            overflow: auto;
            padding: 18px;
            flex: 1;
            flex-direction: column;

            pre {
              line-height: 1.5;
              white-space: pre-wrap;
              white-space: -moz-pre-wrap;
              white-space: -pre-wrap;
              white-space: -o-pre-wrap;
              word-wrap: break-word;
            }
          }
        }
      }
    }
  }
</style>
